<!doctype html><html lang=cn-zh><head><meta charset=UTF-8><meta name=viewport content="width=device-width,initial-scale=1"><meta http-equiv=X-UA-Compatible content="IE=edge"><style type=text/css>body{font-family:monospace}</style><title>[python]记录关于websocket的原理和使用</title>
<meta name=description content="A blog maintained by Vimiix."><link rel=stylesheet href=/css/style.css><script>var _hmt=_hmt||[];(function(){var e,t=document.createElement("script");t.src="https://hm.baidu.com/hm.js?7c24231917964240bae97e813810620c",e=document.getElementsByTagName("script")[0],e.parentNode.insertBefore(t,e)})()</script></head><body><header>====================<br>== Hi, I'm Vimiix ==<br>====================<div style=float:right;color:gray;font-size:x-large>Get hands dirty.</div><br><p><nav><a href=https://www.vimiix.com/><b>首页</b></a>.
<a href=/posts/><b>文章列表</b></a>.
<a href=/projects/><b>开源项目</b></a>.
<a href=/tags/><b>标签</b></a>.
<a href=/friends/><b>友链</b></a>.
<a href=/about/><b>关于我</b></a>.
<a href=/index.xml><b>RSS</b></a>.</nav></p></header><main><article><h1>[python]记录关于websocket的原理和使用</h1><b><time>2018.04.02 19:20</time></b>
<a href=/tags/python>Python</a>
<a href=/tags/websocket>websocket</a>
<a href=/tags/note>note</a>
<a href=/tags/django>django</a><div><h2 id=什么是-websocket>什么是 websocket</h2><p>WebSocket 是一种在单个 TCP 连接上进行<a href=https://zh.wikipedia.org/wiki/%E5%85%A8%E9%9B%99%E5%B7%A5>全双工</a>通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。在 WebSocket API 中，浏览器和服务器只需要完成一次握手，两者之间就直接可以创建<strong>持久性</strong>的连接，并进行双向数据传输。</p><p>Websocket 是一个持久化的协议，相对于 HTTP 这种非持久的协议来说的。</p><p>举个例子：</p><blockquote><p>HTTP 的生命周期通过 Request 来界定，也就是发送一次 Request，收到一次 Response ，那么在 HTTP1.0 中，这次 HTTP 请求就结束了</p><p>在 HTTP1.1 中进行了改进，使得有一个 keep-alive，也就是说，在一个 HTTP 连接中，可以发送多个 Request，接收多个 Response。但是请记住 Request = Response ， 在 HTTP 中永远是这样，也就是说一个 request 只能有一个 response。而且这个 response 也是被动的，不能主动发起。</p><p>而对于 websocket 来说，在 HTTP 的握手基础上建立起链接，服务器端可以主动的向客户端发送数据。</p></blockquote><h2 id=为什么要使用-websocket>为什么要使用 websocket</h2><p>现在，很多网站为了实现页面上的数据更新，都是通过 ajax 技术<a href=https://zh.wikipedia.org/wiki/%E8%BC%AA%E8%A9%A2>轮询</a>去服务器拉取数据。轮询是在特定的时间间隔（如每 1 秒），由浏览器对服务器发出 HTTP 请求，然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点，即浏览器需要不断的向服务器发出请求，然而 HTTP 请求可能包含较长的头部，其中真正有效的数据可能只是很小的一部分，显然这样会浪费很多的带宽等资源。</p><p>而比较新的技术去做轮询的效果是<a href=https://zh.wikipedia.org/wiki/Comet_(web%E6%8A%80%E6%9C%AF)>Comet</a>。Comet 技术又可以分为长轮询和流技术。长轮询改进了上述的轮询技术，减小了无用的请求。它会为某些数据设定过期时间，当数据过期后才会向服务端发送请求；这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个 HTTP 长连接，服务端会不断更新连接状态以保持 HTTP 长连接存活；这样的话，服务端就可以通过这条长连接主动将数据发送给客户端；流技术在大并发环境下，可能会考验到服务端的性能。这两种技术都是基于请求-应答模式，都不算是真正意义上的实时技术；它们的每一次请求、应答，都浪费了一定流量在相同的头部信息上，并且开发复杂度也较大。</p><p>在这种情况下，HTML5 定义了 WebSocket 协议，能更好的节省服务器资源和带宽，并且能够更实时地进行通讯。</p><p>Websocket 使用 ws 或 wss 的统一资源标志符，类似于 HTTPS，其中 wss 表示在 TLS 之上的 Websocket。如：</p><pre tabindex=0><code>ws://example.com/wsapi
wss://secure.example.com/
</code></pre><p>Websocket 使用和 HTTP 相同的 TCP 端口，可以绕过大多数防火墙的限制。默认情况下，Websocket 协议使用 80 端口；运行在 TLS 之上时，默认使用 443 端口。</p><p>WebSocket 目前由 W3C 进行标准化。WebSocket 已经受到 Firefox 4、Chrome 、Opera 10.70 以及 Safari 5 等浏览器的支持。</p><h2 id=一个典型的-websocket-的握手请求>一个典型的 websocket 的握手请求</h2><ul><li>客户端请求：</li></ul><pre tabindex=0><code>GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
</code></pre><ul><li>服务端回应：</li></ul><pre tabindex=0><code>HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
</code></pre><h3 id=字段说明>字段说明</h3><ul><li>Connection 必须设置 Upgrade，表示客户端希望连接升级。</li><li>Upgrade 字段必须设置 Websocket，表示希望升级到 Websocket 协议。</li><li>Sec-WebSocket-Key 是随机的字符串，服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。把“Sec-WebSocket-Key”加上一个特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”，然后计算 SHA-1 摘要，之后进行 BASE-64 编码，将结果做为“Sec-WebSocket-Accept”头的值，返回给客户端。如此操作，可以尽量避免普通 HTTP 请求被误认为 Websocket 协议。</li><li>Sec-WebSocket-Version 表示支持的 Websocket 版本。RFC6455 要求使用的版本是 13，之前草案的版本均应当弃用。</li><li>Origin 字段是可选的，通常用来表示在浏览器中发起此 Websocket 连接所在的页面，类似于 Referer。但是，与 Referer 不同的是，Origin 只包含了协议和主机名称。</li><li>其他一些定义在 HTTP 协议中的字段，如 Cookie 等，也可以在 Websocket 中使用。</li></ul><h2 id=如何使用-websocket>如何使用 websocket</h2><p>关于如何在 Python 后端使用 websocket 的技术有多种方式，可以参考这篇文章<a href=https://letus.club/2016/04/10/python-websocket/>《python 使用 websocket 的几种方式》</a></p><p>我们的项目使用的是 Django 的框架，我这次的实践主要围绕 Django 的 websocket 插件 <strong>dwebsocket</strong> 展开记录。<a href=https://github.com/duanhongyi/dwebsocket>源码地址</a></p><h3 id=安装-dwesocket>安装 dwesocket</h3><p>话不多说， pip 大法。</p><pre tabindex=0><code>pip install dwesocket
</code></pre><h3 id=使用>使用</h3><p>如果希望一个 view 视图既可以处理 HTTP 请求，也可以处理 websocket 请求，只需要将这个 view 函数用装饰器 <code>@accept_websocket</code> 包裹即可。也可以使用 <code>@require_websocket</code> 装饰器，这样的话这个视图只允许接受 websocket 请求，会拒绝正常的 HTTP 请求。</p><p>如果你希望在应用程序中为所有的 url 提供 websocket 请求支持，则可以使用中间件。只需要将<code>dwebsocket.middleware.websocketmiddleware</code> 添加到设置中的的 <code>MIDDLEWARE_CLASSES</code> 中。 （如果没有需要自己定义，不定义会报错，这个有点尴尬，对于这一点的优化，我已经对主库提了一个**<a href=https://github.com/duanhongyi/dwebsocket/pull/35>Pull Request</a>**,希望作者还在维护&mldr;.）</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span>MIDDLEWARE_CLASSES <span style=color:#f92672>=</span> [<span style=color:#e6db74>&#39;dwebsocket.middleware.WebSocketMiddleware&#39;</span>]
</span></span></code></pre></div><p>如果允许每个单独的视图接受 websocket 请求，在 settings 中设置 <code>WEBSOCKET_ACCEPT_ALL</code> 为 <code>True</code>.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span>WEBSOCKET_ACCEPT_ALL <span style=color:#f92672>=</span> <span style=color:#66d9ef>True</span>
</span></span></code></pre></div><h3 id=接口和属性>接口和属性</h3><ol><li><p><strong>request.is_websocket()</strong></p><p>如果是个 websocket 请求返回 True，如果是个普通的 http 请求返回 False,可以用这个方法区分它们。</p></li><li><p><strong>request.websocket</strong></p><p>在一个 websocket 请求建立之后，这个请求将会有一个 websocket 属性，用来给客户端提供一个简单的 api 通讯，如果 request.is_websocket()是 False，这个属性将是 None。</p></li><li><p><strong>WebSocket.wait()</strong></p><p>返回一个客户端发送的信息，在客户端关闭连接之前他不会返回任何值，这种情况下，方法将返回 None</p></li><li><p><strong>WebSocket.read()</strong></p><p>如果没有从客户端接收到新的消息，read 方法会返回一个新的消息，如果没有，就不返回。这是一个替代 wait 的非阻塞方法</p></li><li><p><strong>WebSocket.count_messages()</strong></p><p>返回消息队列数量</p></li><li><p><strong>WebSocket.has_messages()</strong></p><p>如果有新消息返回 True，否则返回 False</p></li><li><p><strong>WebSocket.send(message)</strong></p><p>向客户端发送消息</p></li><li><p><strong>WebSocket.<strong>iter</strong>()</strong></p><p>websocket 迭代器</p></li></ol><h3 id=实践例程>实践例程</h3><p>从客户端接收一条消息，将该消息发送回客户端并关闭连接（通过视图返回）：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span><span style=color:#f92672>from</span> dwebsocket <span style=color:#f92672>import</span> require_websocket
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#a6e22e>@require_websocket</span>
</span></span><span style=display:flex><span><span style=color:#66d9ef>def</span> <span style=color:#a6e22e>echo</span>(request):
</span></span><span style=display:flex><span>    message <span style=color:#f92672>=</span> request<span style=color:#f92672>.</span>websocket<span style=color:#f92672>.</span>wait()
</span></span><span style=display:flex><span>    request<span style=color:#f92672>.</span>websocket<span style=color:#f92672>.</span>send(message)
</span></span></code></pre></div><p>我们也可以让服务端不自动关闭连接,下面的例程中，服务器端会将客户端发来的消息转为小写发送回去，并增加了普通的 HTTP 请求的响应，也做同样操作返回：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span><span style=color:#f92672>from</span> django.http <span style=color:#f92672>import</span> HttpResponse
</span></span><span style=display:flex><span><span style=color:#f92672>from</span> dwebsocket <span style=color:#f92672>import</span> accept_websocket
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#66d9ef>def</span> <span style=color:#a6e22e>modify_message</span>(message):
</span></span><span style=display:flex><span>    <span style=color:#66d9ef>return</span> message<span style=color:#f92672>.</span>lower()
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#a6e22e>@accept_websocket</span>
</span></span><span style=display:flex><span><span style=color:#66d9ef>def</span> <span style=color:#a6e22e>lower_case</span>(request):
</span></span><span style=display:flex><span>    <span style=color:#66d9ef>if</span> <span style=color:#f92672>not</span> request<span style=color:#f92672>.</span>is_websocket(): <span style=color:#75715e># 普通HTTP请求</span>
</span></span><span style=display:flex><span>        message <span style=color:#f92672>=</span> request<span style=color:#f92672>.</span>GET[<span style=color:#e6db74>&#39;message&#39;</span>]
</span></span><span style=display:flex><span>        message <span style=color:#f92672>=</span> modify_message(message)
</span></span><span style=display:flex><span>        <span style=color:#66d9ef>return</span> HttpResponse(message)
</span></span><span style=display:flex><span>    <span style=color:#66d9ef>else</span>: <span style=color:#75715e># websocket请求</span>
</span></span><span style=display:flex><span>        <span style=color:#66d9ef>for</span> message <span style=color:#f92672>in</span> request<span style=color:#f92672>.</span>websocket:
</span></span><span style=display:flex><span>            message <span style=color:#f92672>=</span> modify_message(message)
</span></span><span style=display:flex><span>            request<span style=color:#f92672>.</span>websocket<span style=color:#f92672>.</span>send(message)
</span></span></code></pre></div><h3 id=改变-dwebsocket-后端>改变 dwebsocket 后端</h3><p>目前 dwebsocket 支持两种后端，分别是 <strong>default</strong> 的和 <strong>uwsgi</strong>。</p><p>默认 <strong>default</strong> 支持 <strong>Django 自身的开发服务器</strong>, <strong>eventlent</strong>, <strong>gevent</strong>, 和<strong>gunicore</strong>。</p><p>如果你想使用<strong>uwsgi</strong>后端，在<strong>settings.py</strong>文件中添加 <strong>WEBSOCKET_FACTORY_CLASS</strong>：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span>WEBSOCKET_FACTORY_CLASS <span style=color:#f92672>=</span> <span style=color:#e6db74>&#39;dwebsocket.backends.uwsgi.factory.uWsgiWebSocketFactory&#39;</span>
</span></span></code></pre></div><p>运行 uwsgi ：</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span>uwsgi --http :8080 --http-websockets --processes <span style=color:#ae81ff>1</span> <span style=color:#ae81ff>\
</span></span></span><span style=display:flex><span><span style=color:#ae81ff></span>--wsgi-file wsgi.py--async <span style=color:#ae81ff>30</span> --ugreen --http-timeout <span style=color:#ae81ff>300</span>
</span></span></code></pre></div><h2 id=websocket-的优缺点>websocket 的优缺点</h2><h3 id=优点>优点</h3><ul><li><p>服务器与客户端之间交换的标头信息很小，大概只有 2 至 10 字节（和数据包长度有关）;</p></li><li><p>客户端与服务器都可以主动传送数据给对方;</p></li><li><p>不用频率创建 TCP 请求及销毁请求，减少网络带宽资源的占用，同时也节省服务器资源;</p></li><li><p>更强的实时性。由于协议是全双工的，所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应，延迟明显更少;</p></li><li><p>更好的二进制支持。Websocket 定义了二进制帧，相对 HTTP，可以更轻松地处理二进制内容;</p></li><li><p>可以支持扩展。Websocket 定义了扩展，用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。</p></li></ul><h3 id=缺点>缺点</h3><p>下面这些出自知乎，优点鸡肋的缺点，就是对开发者技术要求高点应该不算缺点，新的技术总有个普及的过程。</p><p>对前端开发者：</p><ul><li>往往要具备数据驱动使用 javascript 的能力，且需要维持住 ws 连接（否则消息无法推送）</li></ul><p>对于后端开发者：</p><ul><li><p>一是长连接需要后端处理业务的代码更稳定（不要随便把进程和框架都 crash 掉）</p></li><li><p>二是推送消息相对复杂一些</p></li><li><p>三是成熟的 http 生态下有大量的组件可以复用，websocket 比较新</p></li></ul><h2 id=参考资料>参考资料</h2><ul><li><p>[1]<a href=https://zh.wikipedia.org/wiki/WebSocket>https://zh.wikipedia.org/wiki/websocket</a></p></li><li><p>[2]<a href=https://github.com/duanhongyi/dwebsocket>https://github.com/duanhongyi/dwebsocket</a></p></li><li><p>[3]<a href=https://www.zhihu.com/question/20155314>websocket 协议有哪些劣势和缺点？</a></p></li></ul></div></article></main><aside><div><div><h3>LATEST POSTS</h3></div><div><ul><li><a href=/posts/2025-10-16-kubernetes-apiserver-authorization-mechanism/>Kubernetes APIServer 鉴权机制</a></li><li><a href=/posts/2025-09-30-kubernetes-apiserver-authentication-mechanism/>Kubernetes APIServer 认证机制</a></li><li><a href=/posts/2024-12-16-deploy-kubernetes-by-kubeadm/>使用 kubeadm 搭建 kubernetes 集群</a></li><li><a href=/posts/2024-09-20-how-to-code-review/>如何做code review</a></li><li><a href=/posts/2024-08-12-weakref-in-python/>Python中的弱引用</a></li></ul></div></div></aside><footer><p>Social Links:
<a href=https://github.com/vimiix><b>Github</b></a>.
<a href=https://www.douban.com/people/vimiix/><b>Douban</b></a>.
<a href=mailto:i@vimiix.com><b>Email</b></a>.<br><hr>&copy; 2017-2025
Vimiix Yao; All rights reserved.
<a href=https://beian.miit.gov.cn/>京ICP备19015214号-1</a></p><script src=https://l2dwidget.js.org/lib/L2Dwidget.min.js></script><script>L2Dwidget.init({model:{jsonPath:"https://unpkg.com/live2d-widget-model-tororo@1.0.5/assets/tororo.model.json"}})</script></footer></body></html>